

/**
 *
 */
package ai

import gui.PitchBoardGUI
import gui.PitchGUI
import gui.customControl.LogWindow

import java.awt.Color
import java.util.Set

import ai.human.PassKnowBase
import ai.human.PlayerGroup
import ai.human.PlayerGroupsManager
import data.Player
import data.Position
import data.Team
import data.Data.PlayerState
import data.Data.Role
import engine.ActionManager
import engine.IGameEngine
import engine.Pitch




/**
 *
 *	AI engine for the human race. 
 *
 * @author Tomasz Dąbrowski
 *
 */
class AIEngineHuman extends AIEngine {

	/**
	 * Debug flags.
	 */
	static Map DEBUG = [ "pathes" : false, "playmakers" : false, "support" : false, "newround" : false, "kbase" : false]
	/**
	 * Timer - last time.
	 */
	static def time = System.nanoTime()
	/**
	 * Timer - show elapsed time
	 */
	static def showTimer = { method ->
		def elapsed = ((System.nanoTime() - time) / 10.power(6)).toInteger().toString() + " ms"
		//System.out.println("\t$method - elapsed time: $elapsed");
		time = System.nanoTime()
	}
	/**
	 * Timer reset.
	 */
	static def resetTimer = {
		time = System.nanoTime()
	}

	/**
	 * Debug - random colors generation.
	 */
	static def DEBUG_RANDOM_COLORS = {
		List colors = (0f..<15f).collect {
			int rgb = Color.HSBtoRGB((float) (it/5f + 0.066f * (int)(it/5) ), 1f, 1f)
			new Color(rgb)
		}
		colors
	}

	/**
	 * Groups manager.
	 */
	PlayerGroupsManager groupsManager
	/**
	 * Player pathes.
	 */
	Map<Player, List<Position>> pathes = [:]
	/**
	 * Stucked players.
	 */
	Set<Player> stucked = []

	/**
	 * Checks if enemy player.
	 */
	def isEnemyPlayer = { Player p ->
		p && !(p in team.players())
	}

	/**
	 * Gets distance to bounds.
	 */
	def distanceToBounds = { Position pos ->
		[
			pos.x(),
			Pitch.PITCH_WIDTH - 1 - pos.x(),
			pos.y(),
			Pitch.PITCH_HEIGHT - 1 - pos.y()
		].min()
	}

	/**
	 * Creates a new ai engine.
	 *
	 * @param team controlled team
	 * @param gameEngine game engine
	 *
	 * @author Tomasz Dąbrowski
	 */
	AIEngineHuman(Team team, IGameEngine gameEngine) {
		super(team, gameEngine)
	}

	/**
	 * Creates players groups.
	 */
	public void createGroups() {
		groupsManager = new PlayerGroupsManager(aiEngine : this, players : team.players())
		groupsManager.generatePositionReqs()
	}

	/**
	 * Set player on the fields.
	 */
	public Position setPlayer() {
		if (!startPositions) {
			assert groupsManager
			startPositions = groupsManager.startPositions
		}
		assert startPositions
		List playerPos = startPositions.remove(0)
		gameEngine.placePlayer(playerPos[0], playerPos[1])
		playerPos[1]
	}

	/* (non-Javadoc)
	 * @see ai.AIEngine#getTouchBackPlayer()
	 */
	@Override
	protected Player getTouchBackPlayer() {
		PlayerGroup pg = groupsManager.groups.find {
			it.mainPlayer.role() == Role.THROWER
		} ?: groupsManager.groups.find { it.mainPlayer }
		groupsManager.updateLeaderGroup(pg)
		LogWindow.instance().Log("The leader group is a group of ${pg.mainPlayer}.")
		pg.mainPlayer
	}

	/* (non-Javadoc)
	 * @see ai.AIEngine#playerAction()
	 */
	@Override
	public boolean playerAction() {
		if(isNextRound()) {
			return false
		}

		if(getAndSetNewRound()) {
			stucked.clear()
			newRound = false
			resetTimer()
			updateDangerValues()
			showTimer("updateDangerValues")
			PlayerGroup leaderGroup = groupsManager.updateLeaderGroup()
			showTimer("updateLeaderGroup")
			pathes = generatePathes()

			LogWindow.instance().Log("The leader group is a group of ${leaderGroup.mainPlayer}.")
			LogWindow.instance().Log("Pathes generated:");
			pathes.each { k, v ->
				LogWindow.instance().Log("$k -> $v");
			}
			DEBUG["newround"] && PitchGUI.getInstance().getAndSetDebugStep()
		}

		if(pathes.every { Player p, List path ->
			p.currentMA() == 0 || path.size() == 0
		}) {
			return false
		}

		def movePlayer = { Player p, List path ->
			Player other = gameEngine.playingField.getPlayer(path[0])
			if(other) {
				if(path.size() > 1) {
					stucked.add(p)
					return false
				} else {
					return false
				}
			}
			if(gameEngine.movePlayer(p, path[0])) {
				stucked.remove(p)
				return true
			} else {
				return false
			}
		}

		if(DEBUG["pathes"]) {
			PitchGUI.instance.getAndSetDebugStep()
			List colors = DEBUG_RANDOM_COLORS()
			List hiPathes = pathes.collect {
				[
					colors.remove(0),
					[it.key],
					it.value
				]
			}
			PitchBoardGUI.getInstance().highlightCells(hiPathes.collectEntries {
				[it[0], it[1]]
			}, false)
			PitchBoardGUI.getInstance().highlightCells(hiPathes.collectEntries {
				[it[0], it[2]]
			}, true)
			PitchGUI.instance.getAndSetDebugStep()
			PitchBoardGUI.getInstance().clearHighlightCells()
		}


		def getBallPlayerPosition = {
			[
				getPlayerWithBall(),
				getBallPosition()
			]
		}
		def ballPlayerPosition = getBallPlayerPosition()
		def leaderWithBall = groupsManager.leaderGroup.mainPlayer.find {
			it.hasBall() && !team.hasThrown()
		}
		def leader = { groupsManager.leaderGroup.mainPlayer }
		def isSupport = { it in groupsManager.support }
		def closeEnemies = { Position pos ->
			posRange(pos).collect {
				gameEngine.playingField.getPlayer(it).find { Player enemy ->
					enemy.state() == PlayerState.IN_PLAY && isEnemyPlayer(enemy)
				}
			}.findAll()
		}

		if(leaderWithBall) {
			Position passPos = getPassPosition(leaderWithBall)
			if(passPos) {
				assert leaderWithBall.distance(passPos) <= ActionManager.RULER_LONG_BOMB
				LogWindow.instance().Log("Player ${leaderWithBall} is throwing the ball.")
				if(gameEngine.resolveThrowBall(leaderWithBall, passPos)) {
					newRound = true
					return true
				}
			}
		}

		def moved = pathes.findAll {
			it.key.currentMA() > 0
		}.sort { a, b ->
			(getDangerValue(new Position(a.key)) <=> getDangerValue(new Position(b.key)))
		}.find { Player p, List path ->
			if(isNextRound()) {
				return false
			}
			Position playerPos = new Position(p)
			//			List enemies = closeEnemies(playerPos)
			List enemies = posRange(playerPos).collect {
				gameEngine.playingField.getPlayer(it)
			}.findAll {
				it && it.state() == PlayerState.IN_PLAY  && !(it in team.players())
			}
			Player ballEnemy
			switch(p) {
				case { p.state() != PlayerState.IN_PLAY }:
					p.setCurrentMA(0)
					false
					break
				case { path && path.first().distance(p) > 1 }:
					stucked.add(p)
					true
					break
				case {
					ballEnemy = enemies.find { it.hasBall() } }:
				case { enemies && isSupport(p) }:
					Player enemy = ballEnemy ?: enemies.find { it.hasBall() } ?: enemies.sort { PlayerGroup.playerPower(it) }.find()
					assert enemy : "enemies = $enemies, player = $p, leader = $leader"
					boolean result = gameEngine.resolveBlock(p, enemy)
					if(isNextRound() || !result) {
						return false
					}
					updateDangerValues()
					true
					break
				case { path }:
					if(playerPos == path[0]) {
						assert path.size() == 1
						//LogWindow.instance().Log("$p on destination.")
						path.remove(0)
						true
					} else {
						boolean moved = movePlayer(p, path)
						if(isNextRound()) {
							return false
						}
						//LogWindow.instance().Log("Player $p path is $path.")
						moved && path.remove(0)
						moved
					}
					break
				default:
					false
					break
			}
		}

		!moved && LogWindow.instance().Log("No one moves, stucked: $stucked.")
		if(isNextRound()) {
			return false
		}

		switch(moved) {
			case { !it && stucked }:
				Player p = stucked.find { pathes[it] }
			//LogWindow.instance().Log("Rerouting stucked player $p.")
				List path = pathes[p]
				int dValue = Math.min(getDangerValue(path.last()), NEAR_ENEMY_DANGER_VALUE - 1)
				pathes[p] = getPath(new Position(p), path.last(), false) ?: getPath(new Position(p), path.last(), false, dValue)
				stucked.remove(p)
				return true
			case { !it }:
				LogWindow.instance().Log("No one moves.")
				return false
			case {
				moved && gameEngine.checkForTouchDown(moved.key)
			}:
				PassKnowBase.getInstance(team.id, this).commitKB(true)
				LogWindow.instance().Log("Player ${moved.key} touchdowns!.")
				return false
			//			case false:
			//				return false
			default:
				def newBallPlayerPosition = getBallPlayerPosition()
				if(ballPlayerPosition[0] != newBallPlayerPosition[0]) {
					ballPlayerPosition = newBallPlayerPosition
					//LogWindow.instance().Log("Player ${newBallPlayerPosition[0]} has ball now.")
					newRound = true
					return true
				} else {
					return moved
				}
		}

	}


	/**
	 * Gets a pass position.
	 *
	 * @param player passing player
	 * @return pass position or null if pass isn't recommended
	 *
	 * @author Tomasz Dąbrowski
	 */
	public Position getPassPosition(Player player) {

		List players = (groupsManager.mainPlayers - player).findAll {
			player.distance(it) <= ActionManager.RULER_LONG_BOMB
		}
		if(!players) {
			return null
		}
		Player other = PassKnowBase.getInstance(team.id, this).getAction(player, players)
		if(other == player) {
			null
		} else {
			other.collect { new Position(it) }
		}
	}

	/**
	 * Generates player pathes.
	 *
	 * @return map of player - path keys
	 *
	 * @author Tomasz Dąbrowski
	 */
	public Map<Player, List<Position>> generatePathes() {
		Map<Player, Position> goals = [:].asSynchronized()

		resetTimer()
		PlayerGroup leaderGroup = groupsManager.leaderGroup
		Position leaderPos = assignLeaderPosition(leaderGroup.mainPlayer)
		assert leaderPos
		goals.put(leaderGroup.mainPlayer, leaderPos)
		showTimer("assignLeaderPosition")

		List playmakers = (groupsManager.groups - leaderGroup).collect { it.mainPlayer }
		List playmakerPoss = generatePlaymakersPositions(goals[leaderGroup.mainPlayer], playmakers)
		goals.putAll(assingPlaymakersPositions(leaderPos, playmakers, playmakerPoss))
		showTimer("assingPlaymakersPositions")


		List colors = DEBUG_RANDOM_COLORS()

		List threads = groupsManager.groups.collect { PlayerGroup pg ->
			new Thread ({
				Map poss = generateSupportPositions(pg.mainPlayer, goals[pg.mainPlayer])
				Map asign = assignSupportPositions(pg.supportPlayers, poss)
				if(DEBUG["support"]) {
					PitchBoardGUI.getInstance().highlightCells( asign.collectEntries {
						[
							colors.remove(0),
							[it.key, it.value]
						]
					}, false )
					PitchGUI.instance.getAndSetDebugStep()
					PitchBoardGUI.instance.clearHighlightCells()
				}
				goals.putAll(asign)
			})
		}
		threads.each { it.start() }
		threads.each { it.join() }
		
		showTimer("assignSupportPositions")

		pathes = goals.collectEntries { Player p, Position goal ->
			int dValue = Math.min(getDangerValue(goal), NEAR_ENEMY_DANGER_VALUE - 1)
			List path = getPath(new Position(p), goal, true, dValue) ?: getPath(new Position(p), goal, true)
			assert path : "p = ${p}, goal = ${goal}"
			[p, path]
		}
		showTimer("getPath")
		pathes
	}


	/**
	 * Gets a path.
	 *
	 * @param p source position
	 * @param dst destination position
	 * @param ignoreEnemiesOnly if true, move around each team players
	 * @return path
	 *
	 * @author Tomasz Dąbrowski
	 */
	public List<Position> getPath(Position p, Position dst, boolean ignoreEnemiesOnly, int dValue = Integer.MAX_VALUE) {
		assert p != Player
		if(p.distance(dst) <= 1) {
			return [dst]
		}

		//		int dValue = getDangerValue(dst)
		//		dValue = dValue >= NEAR_ENEMY_DANGER_VALUE ? (NEAR_ENEMY_DANGER_VALUE-1) : dValue

		List closed = [p]
		Map opened = posRange(p).findAll {
			Player player = gameEngine.playingField.getPlayer(it)
			!player || (ignoreEnemiesOnly && !isEnemyPlayer(player))
		}.collectEntries { Position pos ->
			[pos, pos.distanceAlt(dst)]
		}

		Position current = p
		int iteration = 0
		while(current.distance(dst) > 1) {
			iteration++

			Map groupedOpened = opened.groupBy { it.value }
			groupedOpened = groupedOpened.min { it.key }.value
			Position minPos = (groupedOpened.min { getDangerValue(it.key) }).key
			assert minPos

			List ns = posRange(minPos, 1, true).findAll {
				it in opened.keySet()
			}.sort { Position pos ->
				pos.distanceAlt(dst)
			}
			assert ns && opened
			Map nsDanger = ns.collectEntries {
				[it, getDangerValue(it)]
			}
			current = nsDanger.find { it.value <= dValue }?.key ?: nsDanger.min { it.value }.key
			assert current

			opened.remove(current)
			closed.add(current)
			//assert opened.keySet().disjoint(closed)
			ns = posRange(current).findAll {
				if(it in closed || it in opened.keySet() || (dValue < NEAR_ENEMY_DANGER_VALUE && getDangerValue(it) >= NEAR_ENEMY_DANGER_VALUE)) {
					false
				} else if(!ignoreEnemiesOnly) {
					!gameEngine.playingField.getPlayer(it)
				} else {
					true
				}
			}
			opened.putAll( ns.collectEntries { Position pos ->
				[pos, pos.distanceAlt(dst)]
			} )
			if(!opened) {
				return null
			}
			//assert !opened.isEmpty() : "p = ${p}, dst = ${dst}, closed.size = ${closed.size()}, iteration = $iteration"
		}
		List path = extractPath(closed + current + dst)
		assert path[0] == p
		path.remove(0)
		path
	}

	/**
	 * Extracts the path of a positions list.
	 *
	 * @param path positions list
	 * @return path
	 *
	 * @author Tomasz Dąbrowski
	 */
	public List<Position> extractPath(List<Position> path) {
		ListIterator it = path.listIterator(path.size())
		while(it.previousIndex() > 1) {
			Position pos = it.previous()
			int index = path.subList(0, it.previousIndex()).findIndexOf { Position posIt ->
				posIt.distance(pos) == 1
			}
			while(index != -1 && index < it.previousIndex()) {
				it.previous()
				it.remove()
			}
		}
		path
	}

	/**
	 * Generates support players positions.
	 *
	 * @param mainPlayer leader of the group
	 * @param mpPos leader's destination
	 * @return grouped map of positions
	 *
	 * @author Tomasz Dąbrowski
	 */
	public Map generateSupportPositions(Player mainPlayer, Position mpPos) {
		assert mainPlayer
		assert mpPos
		def poss = AIEngine.posRange(mpPos, mainPlayer.MA()).findAll { Position pos ->
			!isEnemyPlayer(gameEngine.playingField.getPlayer(pos))
		}
		assert poss

		List tackleZones = []
		List enemies = []
		poss.removeAll { Position pos ->
			//			if(pos in tackleZones.flatten()) {
			//				true
			//			} else
			Player p = gameEngine.playingField.getPlayer(pos)
			if (p && !(p in team.players())) {
				true
			} else if( getDangerValue(pos) >= NEAR_ENEMY_DANGER_VALUE ) {
				List enemyPoss = posRange(pos).findAll { Position enPos ->
					isEnemyPlayer(gameEngine.playingField.getPlayer(enPos))
				}
				Position enemyPos = enemyPoss.find { !(it in enemies) }
				if(enemyPos) {
					List enemyTackle = posRange(enemyPos).findAll {
						!isEnemyPlayer(gameEngine.playingField.getPlayer(it))
					}
					tackleZones.add(enemyTackle)
					enemies.add(enemyPos)
				}
				true
			} else {
				false
			}
		}
		tackleZones.sort { it.collect { mpPos.distance(it) }.min() }
		poss = poss.groupBy { getDangerValue(it) }
		poss = poss.sort { 0 - it.key }
		poss.values()*.sort { mpPos.distance(it) }
		["T" : tackleZones, "D" : poss.values()]
	}

	/**
	 * Assigns support positions.
	 *
	 * @param support support players
	 * @param positions positions
	 * @return assignation
	 *
	 * @author Tomasz Dąbrowski
	 */
	public Map<Player, Position> assignSupportPositions(List<Player> support, Map positions) {
		assert support
		assert support.size() <= positions.values().flatten().size()
		Map assign = [:]
		support = support.collect { it }

		positions["T"].each { List poss ->
			if(!support.isEmpty()) {
				Player p = support.min { Player player ->
					poss.collect { player.distance(it) }.min()
				}
				support.remove(p)
				assign.put(p, poss.min { Position pos -> p.distance(pos) })
			}
		}

		if(!support) {
			return assign
		}

		assert positions["D"].flatten().size() >= support.size()
		List poss = []
		Iterator iter = positions["D"].iterator()
		while(support.size().power(2) > poss.size()) {
			assert iter.hasNext()
			poss.addAll(iter.next())
		}

		List possCopy = poss
		poss = []
		(1..(support.size())).each {
			support.each { Player p ->
				Position pos = possCopy.min { p.distance(it) }
				possCopy.remove(pos)
				poss.add(pos)
			}
		}
		
		def evalPerm = { List perm ->
			int v = 0
			perm.eachWithIndex { Position pos, int index ->
				v += pos.distance(support[index])
			}
			v
		}
		
		List assignList = [poss.subList(0, support.size()), evalPerm(poss.subList(0, support.size()))]
		poss.eachPermutation { List perm -> 
			perm = perm.subList(0, support.size())
			int value = evalPerm(perm)
			if(value < assignList[1]) {
				assignList = [perm, value]
			}
		}
		assignList = assignList[0]

//		Set posPerms = poss.permutations().collect { it.subList(0, support.size()) } as Set		
//		List assignList = posPerms.min { List perm ->
//			int v = 0
//			perm.eachWithIndex { Position pos, int index ->
//				v += pos.distance(support[index])
//			}
//			v
//		}
		assert assignList
		assign.putAll( [support, assignList].transpose().collectEntries { it } )
		assert assign
		assign
	}

	/**
	 * Assigns the main player of the leader group position.
	 *
	 * @param leader main pla
	 * @return
	 *
	 * @author Tomasz Dąbrowski
	 */
	public Position assignLeaderPosition(Player leader) {
		def leaderCircle = { List dsts ->
			List circle = posCircle(new Position(leader), leader.MA())
			assert circle.every { leader.distance(it) == leader.MA()}
			Position pos = getSafestDestinationByPath(circle, dsts)[0]
			assert pos.distance(leader) == leader.MA()
			pos
		}

		if(leader.hasBall()) {
			List endZonePoss = Pitch.getEndZonePositions(team.id) as List
			if( endZonePoss.every { leader.distance(it) <= leader.MA() - 1} ) {
				getSafestDestinationByPath([new Position(leader)], endZonePoss)[1]
			} else {
				leaderCircle(endZonePoss)
			}
		} else {
			Position ballPos = ballPosition
			assert ballPos
			Player player = gameEngine.playingField.getPlayer(ballPos)
			if(ballPos.distance(leader) > leader.MA()) {
				leaderCircle(ballPos as List)
			} else if (!player){
				ballPos
			} else if(!isEnemyPlayer(player)) {
				new Position(player)
			} else {
				//assert isEnemyPlayer(player) : "player = $player, ballPos = $ballPos, player.hasBall() = ${player.hasBall()}"
				posRange(ballPos).min { Position pos -> pos.distanceAlt(leader) }
			}
		}
	}

	/**
	 * Generates playmakers positions.
	 *
	 * @param leaderPos leader position
	 * @param playmakers playmakers
	 * @return positions
	 *
	 * @author Tomasz Dąbrowski
	 */
	public List<Position> generatePlaymakersPositions(Position leaderPos, List<Player> playmakers) {
		List map = pitchRange()
		List passRange = ActionManager.RULER_SHORT_PASS..<ActionManager.RULER_LONG_PASS
		List ps
		int iteration = 100
		while(!ps || (ps.size() < map.size() && ps.size() < playmakers.size() * 5.power(2)) && iteration-- > 0) {
			ps = map.findAll { Position pos ->
				(leaderPos.distance(pos) in passRange) && (distanceToBounds(pos)) && getDangerValue(pos) < NEAR_ENEMY_DANGER_VALUE
			}
			passRange = ([
				passRange[0] - 2,
				passRange[0] - 1
			]
			+ passRange + [(passRange.last() + 1)])
			passRange.removeAll { it < 2 || it > ActionManager.RULER_LONG_BOMB }
		}

		ps
	}

	/**
	 * K-means clustering.
	 *
	 * @param groups
	 *
	 * @author Tomasz Dąbrowski
	 */
	public static void kmeansClustering(List groups) {
		def getCentroid = { List group ->
			List xy = [0f, 0f]
			group.each { Position pos ->
				xy[0] += pos.x()
				xy[1] += pos.y()
			}
			xy = xy.collect { it / (float) group.size() }
			new Position(xy[0].round().toInteger(), xy[1].round().toInteger())
		}
		def diff = { Position pos, Position centroid ->
			(pos.x() - centroid.x()).abs() + (pos.y() - centroid.y()).abs()
		}

		def centroids = groups.collect getCentroid
		boolean hasChanged = true
		while(hasChanged) {
			hasChanged = false
			groups.eachWithIndex { list, i ->
				def iter = list.iterator()
				while(iter.hasNext()) {
					Position pos = iter.next()
					def dists = centroids.collect { Position c -> diff(pos, c) }
					int min = dists.indexOf( dists.min() )
					if(min != i) {
						hasChanged = true
						iter.remove()
						groups[min].add(pos)
						centroids[i] = getCentroid(groups[i])
						centroids[min] = getCentroid(groups[min])
					}
				}
			}
		}
		groups
	}

	/**
	 * Gets the closets positions to it's group centroids.
	 *
	 * @param leaderPos leader position
	 * @param groups groups
	 * @return positions
	 *
	 * @author Tomasz Dąbrowski
	 */
	public List getMaxDistancePositions(Position leaderPos, List groups) {
		def dangerGroups = groups.collect { List poss -> poss.groupBy { Position pos -> getDangerValue(pos) } }
		groups = dangerGroups.collect { Map map ->
			map = map.sort { it.key }
			map.values().iterator().next().collect { it }
		}
		//		groups.collect { it.min { leaderPos.distance(it) }}

		def getCenter = { List poss ->
			int x = (int) (poss.sum { Position pos -> pos.x() } / (float) poss.size()).round()
			int y = (int) (poss.sum { Position pos -> pos.y() } / (float) poss.size()).round()
			new Position(x, y)
		}

		groups.collect  { List group ->
			Position center = getCenter(group)
			group.min { Position pos -> pos.distanceAlt(center) }
		}
	}

	/**
	 * Assigns playmakers to positions.
	 *
	 * @param leaderPos leader position
	 * @param playmakers playmakers
	 * @param positions positions
	 * @return assignation
	 *
	 * @author Tomasz Dąbrowski
	 */
	public Map<Player, Position> assingPlaymakersPositions(Position leaderPos, List<Player> playmakers, List<Position> positions) {
		assert playmakers.size() <= positions.size()
		List groups = playmakers.collect { []}
		positions = positions.collect { it }
		(0..<positions.size()).each { i ->
			def groupIdx = i % playmakers.size()
			Position minPos = positions.min { Position pos -> pos.distance( playmakers[groupIdx] ) }
			positions.remove(minPos)
			groups[groupIdx].add(minPos)
		}

		assert groups.flatten().size() == groups.flatten().unique().size() : groups
		kmeansClustering(groups)
		assert groups.every() : groups

		if(DEBUG["playmakers"]) {
			List colors = DEBUG_RANDOM_COLORS()
			Map hiGroups = groups.collectEntries {
				[
					colors.remove(0),
					it
				]
			}
			PitchBoardGUI.getInstance().clearHighlightCells()
			PitchBoardGUI.getInstance().highlightCells(hiGroups, false)
			PitchGUI.instance.getAndSetDebugStep()
		}

		groups = getMaxDistancePositions(leaderPos, groups)

		def assignations = groups.permutations().collect { [playmakers, it].transpose() }
		assignations = assignations.groupBy { it.max { it[0].distance(it[1]) } }
		assignations = assignations.values().iterator().next().min { it.sum { it[0].distance(it[1]) } }
		assignations = assignations.collectEntries { it }

	}
}
